// 1. any unknown  顶级类型  包含下边所有类型（定义的变量可以赋值各种类型的数据）
// 2. Object  对象类型（原型链顶端）  包含下边所有类型
// 3. Number String Boolean  基础类型
// 4. number string boolean 具体类型
// 5. 1     'abc' true 具体值
// 6  never 底层类型 永远无法到达的类型  例如函数抛出异常或者死循环
// any 可以赋值给任意类型的变量，会造成变量污染
// unknown 只能赋值给自身或者any
// unknown 不能读任何属性和方法  unknown比any更安全
// any 可以读写任何属性和方法  但是不安全

// ts继承js的8种基本类型 number string boolean symbol bigint object undefined null
// undefined null 既可以作为值，也可以作为类型
// object类型包含 对象、数组和函数
// Number String Object Boolean 这些大写开头的属于js的内置对象，不是类型
// 但是也可以作为类型使用，
// 比如 let a: Number; 表示a是包装对象number类型，不建议使用大写的这种类型

// ts 其他类型
// 联合类型 |  表示多个类型中的一个（类型放大）  用的时候一般需要 typeof 进行类型判断
// 交叉类型 &  表示多个类型同时满足（类型缩小）通常用来表示对象的合成
let a: number = 999;
console.log(a);

let b: unknown = 999;
let c: any = 0;
c = b;
b = c;
// unknown 经过 类型缩小 后，可以赋值给任意类型变量
// 类型缩小： 将一个不明确的类型缩小为更明确的类型
if (typeof b === 'number') {
  let d: number = b;
}

let o1: object = [];
let o2: object = {};
let o3: object = () => 123;
let o4: {}; // new Object() 也是Object 包含下边所有类型
o4 = 123;
let o5: {} = { a: 123 };
// o5.age = 18  // 报错 不能添加属性  尽量不要用这种方式定义对象
let o6: Object = { a: 123 };
// o6.age = 18  // 报错 不能添加属性

// interface
interface IUser {
  name: string;
  age: number;
}
// interface 属性必须和类型一致，不能多也不能少，重名的时候会把两个合并在一起，属性必须全部包含
let user: IUser = {
  name: "张三",
  age: 18,
};

interface KeyAny {
  readonly id: number; // 只读属性  不可更改的属性或者函数带上readonly修饰符
  name: string;
  age: number;
  hobby?: string; // 可选属性
  readonly cb: () => boolean; // 只读方法
  [key: string]: any; // 索引签名  允许添加任意属性
}

let user2: KeyAny = {
  id: 1,
  name: "张三",
  age: 18,
  sex: "男",
  hobby: "篮球",
  cb: () => {
    return true;
  },
  height: 180,
};
// user2.id = 2; // 只读属性不能修改

interface A {
  name: string;
}
interface C {
  sex: string;
}
// 接口继承，相当与把接口A的属性添加到接口B中
// 接口可以继承多个接口
interface B extends A, C {
  age: number;
}
let ss: B = {
  name: "张三",
  age: 18,
  sex: "男",
};
let ss2: A = {
  name: "张三",
};
// interface 定义函数
interface Fn {
  // ()里是参数，：后边是返回值类型
  (name: string, count: number): string;
}
const fn: Fn = function (name: string, count: number) {
  return name + count;
};

// 定义数组
let arr: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
let arr3: Array<string> = ["a", "b", "c"];
let arr4: Array<any> = [1, "a", true];

const arr333:readonly number[] = [0, 1]; // 只读数组
// arr333[0] = 2; // 错误  删除、修改、新增数组成员都会报错

// 定义对象类型的数组
interface Person {
  name: string;
  age: number;
}
let arr5: Person[] = [
  { name: "张三", age: 18 },
  { name: "张三", age: 18 },
];

// 二维数组
let arr6: number[][] = [
  [1, 2],
  [3, 4],
];
let arr7: Array<Array<number>> = [
  [1, 2],
  [3, 4],
];

// 函数
// 1.函数定义类型和返回值 | 箭头函数定义类型和返回值
// 2.函数默认参数 | 函数可选参数
// 3.参数是一个对象
// 4.函数this类型
// 5.函数重载

function add(x: number, y: number): number {
  return x + y;
}
// 默认值直接赋值，默认值不能和可选参数一起使用
// 可选参数必须放在普通参数最后面，可以放在默认值参数前边,可选参数默认值为undefined
const add2 = (x?: number, y: number = 3): number => {
  if (x === undefined) {
    x = 0;
  }
  return x + y;
};
console.log(add2(undefined, 5));

// rest 参数   ...m 接收剩余参数
function multify(n: number, ...m: number[]) {
  console.log(m, n)
  return m.map(d => n * d)
}
console.log('-----multify-----')
console.log(multify(2, 1, 2, 3));

type Options = {
  title:string;
  darkMode?:boolean;
};
let myOpt = {
  title: 'ceshi',
  darkmode: true  // 拼写错误
}
const obj33: Options = myOpt;  // 可以赋值成功

console.log('-----obj33-----')
console.log(obj33)
// console.log(obj33.darkmode)   // 但是获取的时候ts会报错
// obj33.darkmode = false;

// ts 可以定义this类型，this类型可以指定函数的this指向，在js中无法使用，
// 必须是第一个参数定义this类型
interface Obj {
  arr: number[];
  add: (this: Obj, num: number) => void;
}
let obj: Obj = {
  arr: [1, 2, 3],
  add: function (this: Obj, num: number) {
    this.arr.push(num);
  },
};
obj.add(4);
console.log(obj.arr);

// 重载
const uuu: number[] = [1, 2, 3];
function findNum(id: number): number[]; // 有id的话查询数组中id的元素
function findNum(arr: number[]): number[]; // 是数组的话为添加元素
function findNum(): number[]; // 无参数的话返回数组
function findNum(ids?: number | number[]): number[] {
  if (typeof ids === "number") {
    return uuu.filter((item) => item === ids);
  } else if (Array.isArray(ids)) {
    uuu.push(...ids);
    return uuu;
  } else {
    return uuu;
  }
}
console.log(findNum(3));
console.log(findNum([6, 7]));
console.log(findNum());

// TS类型断言 | 联合类型 | 交叉类型
// 类型断言 as  <>
function fnn2(type: number | string): number {
  if (typeof type === "string") {
    // 类型断言  告诉编译器我知道type的类型
    return (type as string).length; // 另一种类型断言
  } else {
    return type.toString().length;
  }
}
console.log(fnn2("12345"));
console.log(fnn2(12345));
function fnn3(type: any): boolean {
  return type as boolean; // any类型可以断言为任何类型
}
console.log("---fnn3---");
console.log(fnn3(1)); // 1 返回的还是数字
// 联合类型
function getType(type: number | boolean): boolean {
  return !!type; // !!可以将任何类型转换为boolean类型(强制转换) 0 => false  1 => true
}

interface Perss {
  name: string;
  age: number;
}
interface Man {
  sex: string;
}
function fnn(p: Perss & Man): string {
  // 交叉类型  必须同时满足Perss和Man,跟继承类似
  return `我叫${p.name},今年${p.age},性别是${p.sex}`;
}
console.log(fnn({ name: "张三", age: 18, sex: "男" }));

let aaa: [string, boolean] = ["1", false]; // 元组类型
let bbb: [number, string, boolean] = [1, "2", true]; // 元组类型
// 元组类型可以指定每个元素的类型，长度固定
let ccc: [number, string][] = [
  [1, "2"],
  [3, "4"],
]; // 元组类型的数组

// 枚举类型
// const enum Color {
//   Red,
//   Green,
//   Blue,
// }
// let color: Color = Color.Red; // 枚举类型
// console.log(color); // 0
// console.log(Color[0]); // Red
// console.log(Color.Green); // 1
// console.log(Color["Blue"]); // 2

let sss; // 不赋值时，会推断成any类型
sss = 123;
sss = "123";

// 类型别名
// type可以定义基本类型、联合类型、交叉类型等
// type可以定义函数类型、数组类型、元组类型等
// type可以定义对象类型
// type可以定义接口类型
// type可以定义联合类型和交叉类型
// type可以定义类型别名
// type可以定义类型别名，和interface类似，但是type不能继承
type s = string;
let asd: s = "123";

type ss = string | number; // 联合类型
let asd2: ss = "123";
asd2 = 123;

// 交叉类型
// 交叉类型是将多个类型合并为一个类型，包含所有类型的属性和方法
// 交叉类型可以用&符号连接多个类型
type b1 = {
  name: string;
  age: number;
};
type b2 = {
  sex: string;
};
type b3 = b1 & b2; // 交叉类型
let b3Obj: b3 = {
  name: "张三",
  age: 18,
  sex: "男",
};

type SS3 = string & number; // 交叉类型  此时SS3的类型是never，因为string和number没有交集

type Typess = "a" | "b" | "c"; // 枚举类型
function fnn4(type: Typess): void {
  switch (type) {
    case "a":
      console.log("a");
      break;
    case "b":
      console.log("b");
      break;
    case "c":
      console.log("c");
      break;
    default:
      // 当没有枚举完所有情况时，会报错
      // 通过下面的方式可以确保所有情况都被处理
      // 这样当有新的枚举值时，编译器会报错，提醒我们处理新的情况
      const exhaustiveCheck: never = type; // never类型  用于检查是否所有情况都被处理
      break;
  }
}

